package org.ahead4.joint.utils;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>Description: RSA算法加密/解密工具类</p>
 * <p>Copyright: Copyright (c) 2014</p>
 * <p>Company: </p>
 *
 * @author wangyu
 * @version 1.0
 */
public class RSAUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(RSAUtils.class);

    // 该工具类不能被实例化
    private RSAUtils() {
    }

    /**
     * 调用示例
     *
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        String str = "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World";
        str += str;
        str += str;
        str += str;
        str += str;
        // 生成密钥对
        Map<String, String> key = genKeyPair();


        // 获取公钥
        String publicKey = key.get(PUBLIC_KEY);
        System.out.println("公钥：" + publicKey);
        // 获取私钥
        String privateKey = key.get(PRIVATE_KEY);
        System.out.println("私钥：" + privateKey);

        // 签名
        String sign = sign(str.getBytes(StandardCharsets.UTF_8), privateKey);
        System.out.println("签名：" + sign);

        // 验签
        System.out.println("验签：" + verify(str.getBytes(StandardCharsets.UTF_8), publicKey, sign));

        // 公钥加密
        String encryptByPublicKey = encryptByPublicKey(str.getBytes(StandardCharsets.UTF_8), publicKey);
        System.out.println("公钥加密：" + encryptByPublicKey);


        // 私钥解密
        String decryptByPrivateKey = new String(decryptByPrivateKey(encryptByPublicKey.getBytes(), privateKey), "GB2312");
        System.out.println("私钥解密：" + decryptByPrivateKey);
        System.out.println("私钥解密：" + decryptByPrivateKey.equals(str));
        // 私钥加密
        String encryptByPrivateKey = encryptByPrivateKey(str.getBytes(StandardCharsets.UTF_8), privateKey);
        System.out.println("私钥加密：" + encryptByPrivateKey);

        // 公钥解密
        String decryptByPublicKey = new String(decryptByPublicKey(encryptByPrivateKey.getBytes(StandardCharsets.UTF_8), publicKey), StandardCharsets.UTF_8);
        System.out.println("公钥解密：" + decryptByPublicKey);
        System.out.println("公钥解密：" + decryptByPublicKey.equals(str));

    }

    /** 默认的安全服务提供者 */
//    private static final Provider DEFAULT_PROVIDER = new BouncyCastleProvider();
    /**
     * 编码
     */
    public static final String CHARSET = "UTF-8";
    /**
     * 密钥类型
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";// "MD5withRSA";

    /**
     * 加密算法及填充方式
     */
    public static final String ENCRYPT_ARITHMETIC = "RSA/ECB/PKCS1Padding";

    /**
     * 获取私钥的key
     */
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 获取公钥的key
     */
    public static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 默认密钥长度
     */
    private static final int KEY_LENGTH = 1024;

    /**
     * <p>
     * 使用默认值生成密钥对(公钥和私钥)
     * </p>
     *
     * @return
     * @throws Exception
     */
    public static Map<String, String> genKeyPair() {
        return genKeyPairBase64(KEY_LENGTH);
    }

    /**
     * <p>
     * 生成密钥对（公钥和私钥分离）base64转码
     * </p>
     *
     * @return
     * @throws Exception
     */
    public static Map<String, String> genKeyPairBase64(int keyLength) {
        // 创建密钥对
        KeyPair keyPair = genKeyPair(keyLength);
        // 转换
        return convertKeyPairBase64(keyPair);
    }

    /**
     * <p>
     * 将密钥密钥对base64转码
     * </p>
     *
     * @return
     * @throws Exception
     */
    public static Map<String, String> convertKeyPairBase64(KeyPair keyPair) {

        // 公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 容器
        Map<String, String> keyMap = new HashMap<>(2);
        // 转码
        try {
            keyMap.put(PUBLIC_KEY, RSAKeyConvertUtils.encodeBase64(publicKey.getEncoded()));
            // 转码
            keyMap.put(PRIVATE_KEY, RSAKeyConvertUtils.encodeBase64(privateKey.getEncoded()));
        } catch (Exception e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        return keyMap;
    }

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     *
     * @return
     * @throws Exception
     */
    public static KeyPair genKeyPair(int keyLength) {
        // 密钥对
        KeyPair keyPair = null;
        try {
            // 初始化RSA算法
            //KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM,DEFAULT_PROVIDER);
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            // 密钥生成工厂
            //KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, DEFAULT_PROVIDER);

            // 系统时间
            String sysDate = new Date().getTime() + "w";
            // 随机时间
            String random = sysDate.substring(10) + sysDate.substring(0, 10);
            // 长度
            keyPairGen.initialize(keyLength, new SecureRandom(random.getBytes()));
            // 创建
            keyPair = keyPairGen.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        return keyPair;
    }

    /**
     * <p>
     * 用私钥对信息生成数字签名
     * </p>
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return base64编码、CHARSET字符集得签名字符串
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return new String(Base64.encodeBase64(signature.sign()), CHARSET);
    }

    /**
     * <p>
     * 校验数字签名
     * </p>
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * @return 是否通过
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicK);
        signature.update(data);
        return signature.verify(Base64.decodeBase64(sign));
    }

    /**
     * <P>
     * 私钥解密
     * </p>
     *
     * @param encryptedData 已加密数据
     * @param privateKey    私钥(BASE64编码)
     * @return 解密后得比特数组
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey)
            throws Exception {
        // 反转义私钥
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        // base64反转义数据
        encryptedData = Base64.decodeBase64(encryptedData);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        RSAPrivateKeySpec keySpec = keyFactory.getKeySpec(privateK, RSAPrivateKeySpec.class);
        BigInteger prime = keySpec.getModulus();
        int keyLen = prime.toString(2).length();
        int bufferSize = keyLen / 8;
        Cipher cipher = Cipher.getInstance(ENCRYPT_ARITHMETIC);
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > bufferSize) {
                cache = cipher.doFinal(encryptedData, offSet, bufferSize);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * bufferSize;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * <p>
     * 公钥解密
     * </p>
     *
     * @param encryptedData 已加密数据
     * @param publicKey     公钥(BASE64编码)
     * @return 解密后得比特数组
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        // base64反转义数据
        encryptedData = Base64.decodeBase64(encryptedData);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        RSAPublicKeySpec keySpec = keyFactory.getKeySpec(publicK, RSAPublicKeySpec.class);
        BigInteger prime = keySpec.getModulus();
        int keyLen = prime.toString(2).length();
        int bufferSize = keyLen / 8;
        Cipher cipher = Cipher.getInstance(ENCRYPT_ARITHMETIC);
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > bufferSize) {
                cache = cipher.doFinal(encryptedData, offSet, bufferSize);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * bufferSize;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * <p>
     * 公钥加密
     * </p>
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return base64编码、CHARSET字符集得密文字符串
     * @throws Exception
     */
    public static String encryptByPublicKey(byte[] data, String publicKey)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        RSAPublicKeySpec keySpec = keyFactory.getKeySpec(publicK, RSAPublicKeySpec.class);
        BigInteger prime = keySpec.getModulus();
        // 秘钥长度
        int keyLen = prime.toString(2).length();
        // 预测分段大小
        int bufferSize = keyLen / 8 - keyLen / 10;
        // 对数据加密
        Cipher cipher = Cipher.getInstance(ENCRYPT_ARITHMETIC);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 还应有数据
        while (inputLen - offSet > 0) {
            // 当剩余数据长度大于分段长度
            if (inputLen - offSet > bufferSize) {
                // 分段加密
                cache = cipher.doFinal(data, offSet, bufferSize);
            } else {
                // 加密剩余
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            // 已经处理的数据量
            offSet = i * bufferSize;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return new String(Base64.encodeBase64(encryptedData), CHARSET);
    }

    /**
     * <p>
     * 私钥加密
     * </p>
     *
     * @param data       源数据
     * @param privateKey 私钥(BASE64编码)
     * @return base64编码、CHARSET字符集得密文字符串
     * @throws Exception
     */
    public static String encryptByPrivateKey(byte[] data, String privateKey)
            throws Exception {

        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        RSAPrivateKeySpec keySpec = keyFactory.getKeySpec(privateK, RSAPrivateKeySpec.class);
        BigInteger prime = keySpec.getModulus();
        int keyLen = prime.toString(2).length();
        int bufferSize = keyLen / 8 - keyLen / 10;
        Cipher cipher = Cipher.getInstance(ENCRYPT_ARITHMETIC);
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > bufferSize) {
                cache = cipher.doFinal(data, offSet, bufferSize);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * bufferSize;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return new String(Base64.encodeBase64(encryptedData), CHARSET);
    }

    /**
     * <p>
     * 获取私钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(Map<String, Object> keyMap)
            throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return new String(Base64.encodeBase64(key.getEncoded()), CHARSET);
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     *
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPublicKey(Map<String, Object> keyMap)
            throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return new String(Base64.encodeBase64(key.getEncoded()), CHARSET);
    }

}